experimental branch to support using fewer gems for saving RAM

Andrew Cantino 9 years ago
parent
commit
eb52eeeeea

+ 70 - 76
Gemfile

@@ -1,5 +1,24 @@
1 1
 source 'https://rubygems.org'
2 2
 
3
+# Optional libraries.  To conserve RAM, comment out any that you don't need,
4
+# then run `bundle` and commit the updated Gemfile and Gemfile.lock.
5
+gem 'twilio-ruby', '~> 3.11.5'    # TwilioAgent
6
+gem 'ruby-growl', '~> 4.1.0'      # GrowlAgent
7
+gem 'net-ftp-list', '~> 3.2.8'    # FtpsiteAgent
8
+gem 'wunderground', '~> 1.2.0'    # WeatherAgent
9
+gem 'forecast_io', '~> 2.0.0'     # WeatherAgent
10
+gem 'rturk', '~> 2.12.1'          # HumanTaskAgent
11
+gem 'weibo_2', '~> 0.1.4'         # Weibo Agents
12
+gem 'hipchat', '~> 1.2.0'         # HipchatAgent
13
+gem 'xmpp4r',  '~> 0.5.6'         # JabberAgent
14
+gem "google-api-client"           # GoogleCalendarPublishAgent
15
+gem 'mqtt'                        # MQTTAgent
16
+gem 'slack-notifier', '~> 0.5.0'  # SlackAgent
17
+
18
+# Optional Services.
19
+gem 'omniauth-37signals'          # BasecampAgent
20
+# gem 'omniauth-github'
21
+
3 22
 # Bundler <1.5 does not recognize :x64_mingw as a valid platform name.
4 23
 # Unfortunately, it can't self-update because it errors when encountering :x64_mingw.
5 24
 unless Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('1.5.0')
@@ -7,109 +26,67 @@ unless Gem::Version.new(Bundler::VERSION) >= Gem::Version.new('1.5.0')
7 26
   exit 1
8 27
 end
9 28
 
10
-gem 'bundler', '>= 1.5.0'
11
-
12
-gem 'protected_attributes', '~>1.0.8'
13
-
14
-gem 'rails' , '4.1.5'
15
-
16
-case RUBY_PLATFORM
17
-when /freebsd|netbsd|openbsd/
18
-  # ffi (required by typhoeus via ethon) merged fixes for bugs fatal
19
-  # on these platforms after 1.9.3; no following release as yet.
20
-  gem 'ffi', github: 'ffi/ffi', branch: 'master'
21
-
22
-  # tzinfo 1.2.0 has added support for reading zoneinfo on these
23
-  # platforms.
24
-  gem 'tzinfo', '>= 1.2.0'
25
-when /solaris/
26
-  # ditto
27
-  gem 'tzinfo', '>= 1.2.0'
28
-end
29
-
30
-# Windows does not have zoneinfo files, so bundle the tzinfo-data gem.
31
-gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
29
+gem 'protected_attributes', '~>1.0.8' # This must be loaded before some other gems, like delayed_job.
32 30
 
33
-gem 'mysql2', '~> 0.3.16'
34
-gem 'devise', '~> 3.2.4'
35
-gem 'kaminari', '~> 0.16.1'
31
+gem 'ace-rails-ap', '~> 2.0.1'
36 32
 gem 'bootstrap-kaminari-views', '~> 0.0.3'
37
-gem 'rufus-scheduler', '~> 3.0.8', require: false
38
-gem 'json', '~> 1.8.1'
39
-gem 'jsonpath', '~> 0.5.6'
40
-gem 'twilio-ruby', '~> 3.11.5'
41
-gem 'ruby-growl', '~> 4.1.0'
42
-gem 'liquid', '~> 2.6.1'
43
-
33
+gem 'bundler', '>= 1.5.0'
34
+gem 'cantino-twitter-stream', github: 'cantino/twitter-stream', branch: 'master'
35
+gem 'coffee-rails', '~> 4.0.0'
36
+gem 'daemons', '~> 1.1.9'
44 37
 gem 'delayed_job', '~> 4.0.0'
45 38
 gem 'delayed_job_active_record', '~> 4.0.0'
46
-gem 'daemons', '~> 1.1.9'
47
-
39
+gem 'devise', '~> 3.2.4'
40
+gem 'em-http-request', '~> 1.1.2'
41
+gem 'faraday', '~> 0.9.0'
42
+gem 'faraday_middleware'
43
+gem 'feed-normalizer'
48 44
 gem 'foreman', '~> 0.63.0'
49
-
50
-gem 'sass-rails',   '~> 4.0.0'
51
-gem 'coffee-rails', '~> 4.0.0'
52
-gem 'uglifier', '>= 1.3.0'
53
-gem 'select2-rails', '~> 3.5.4'
54
-gem 'jquery-rails', '~> 3.1.0'
55
-gem 'ace-rails-ap', '~> 2.0.1'
56
-gem 'spectrum-rails'
57
-
58
-
59 45
 # geokit-rails doesn't work with geokit 1.8.X but it specifies ~> 1.5
60 46
 # in its own Gemfile.
61 47
 gem 'geokit', '~> 1.8.4'
62 48
 gem 'geokit-rails', '~> 2.0.1'
63
-
49
+gem 'jquery-rails', '~> 3.1.0'
50
+gem 'json', '~> 1.8.1'
51
+gem 'jsonpath', '~> 0.5.6'
52
+gem 'kaminari', '~> 0.16.1'
64 53
 gem 'kramdown', '~> 1.3.3'
65
-gem 'faraday', '~> 0.9.0'
66
-gem 'faraday_middleware'
67
-gem 'typhoeus', '~> 0.6.3'
54
+gem 'liquid', '~> 2.6.1'
55
+gem 'mysql2', '~> 0.3.16'
56
+gem 'multi_xml'
68 57
 gem 'nokogiri', '~> 1.6.1'
69
-gem 'net-ftp-list', '~> 3.2.8'
70
-
71
-gem 'wunderground', '~> 1.2.0'
72
-gem 'forecast_io', '~> 2.0.0'
73
-gem 'rturk', '~> 2.12.1'
74
-
75
-gem "google-api-client"
76
-
77
-gem 'twitter', '~> 5.8.0'
78
-gem 'cantino-twitter-stream', github: 'cantino/twitter-stream', branch: 'master'
79
-gem 'em-http-request', '~> 1.1.2'
80
-gem 'weibo_2', '~> 0.1.4'
81
-gem 'hipchat', '~> 1.2.0'
82
-gem 'xmpp4r',  '~> 0.5.6'
83
-gem 'feed-normalizer'
84
-gem 'slack-notifier', '~> 0.5.0'
85
-gem 'therubyracer', '~> 0.12.1'
86
-gem 'mqtt'
87
-
88 58
 gem 'omniauth'
89 59
 gem 'omniauth-twitter'
90
-gem 'omniauth-37signals'
91
-gem 'omniauth-github'
60
+gem 'rails' , '4.1.5'
61
+gem 'rufus-scheduler', '~> 3.0.8', require: false
62
+gem 'sass-rails',   '~> 4.0.0'
63
+gem 'select2-rails', '~> 3.5.4'
64
+gem 'spectrum-rails'
65
+gem 'therubyracer', '~> 0.12.1'
66
+gem 'twitter', '~> 5.8.0'
67
+gem 'typhoeus', '~> 0.6.3'
68
+gem 'uglifier', '>= 1.3.0'
92 69
 
93 70
 group :development do
94
-  gem 'binding_of_caller'
95 71
   gem 'better_errors', '~> 1.1'
72
+  gem 'binding_of_caller'
96 73
   gem 'quiet_assets'
97 74
 end
98 75
 
99 76
 group :development, :test do
100
-  gem 'vcr'
77
+  gem 'coveralls', require: false
78
+  gem 'delorean'
101 79
   gem 'dotenv-rails'
102 80
   gem 'pry'
103
-  gem 'rspec-rails', '~> 2.99'
81
+  gem 'rr'
104 82
   gem 'rspec', '~> 2.99'
105 83
   gem 'rspec-collection_matchers'
84
+  gem 'rspec-rails', '~> 2.99'
106 85
   gem 'shoulda-matchers'
107
-  gem 'rr'
108
-  gem 'delorean'
109
-  gem 'webmock', '~> 1.17.4', require: false
110
-  gem 'coveralls', require: false
111 86
   gem 'spring'
112 87
   gem 'spring-commands-rspec'
88
+  gem 'vcr'
89
+  gem 'webmock', '~> 1.17.4', require: false
113 90
 end
114 91
 
115 92
 group :production do
@@ -117,6 +94,23 @@ group :production do
117 94
   gem 'rack'
118 95
 end
119 96
 
97
+case RUBY_PLATFORM
98
+  when /freebsd|netbsd|openbsd/
99
+    # ffi (required by typhoeus via ethon) merged fixes for bugs fatal
100
+    # on these platforms after 1.9.3; no following release as yet.
101
+    gem 'ffi', github: 'ffi/ffi', branch: 'master'
102
+
103
+    # tzinfo 1.2.0 has added support for reading zoneinfo on these
104
+    # platforms.
105
+    gem 'tzinfo', '>= 1.2.0'
106
+  when /solaris/
107
+    # ditto
108
+    gem 'tzinfo', '>= 1.2.0'
109
+end
110
+
111
+# Windows does not have zoneinfo files, so bundle the tzinfo-data gem.
112
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
113
+
120 114
 # This hack needs some explanation.  When on Heroku, use the pg, unicorn, and rails12factor gems.
121 115
 # When not on Heroku, we still want our Gemfile.lock to include these gems, so we scope them to
122 116
 # an unsupported platform.

+ 1 - 4
Gemfile.lock

@@ -204,9 +204,6 @@ GEM
204 204
     omniauth-37signals (1.0.5)
205 205
       omniauth (~> 1.0)
206 206
       omniauth-oauth2 (~> 1.0)
207
-    omniauth-github (1.1.2)
208
-      omniauth (~> 1.0)
209
-      omniauth-oauth2 (~> 1.1)
210 207
     omniauth-oauth (1.0.1)
211 208
       oauth
212 209
       omniauth (~> 1.0)
@@ -423,12 +420,12 @@ DEPENDENCIES
423 420
   kramdown (~> 1.3.3)
424 421
   liquid (~> 2.6.1)
425 422
   mqtt
423
+  multi_xml
426 424
   mysql2 (~> 0.3.16)
427 425
   net-ftp-list (~> 3.2.8)
428 426
   nokogiri (~> 1.6.1)
429 427
   omniauth
430 428
   omniauth-37signals
431
-  omniauth-github
432 429
   omniauth-twitter
433 430
   pg
434 431
   protected_attributes (~> 1.0.8)

+ 1 - 1
app/assets/stylesheets/application.css.scss.erb

@@ -170,7 +170,7 @@ span.not-applicable:after {
170 170
 
171 171
 // Disabled
172 172
 
173
-.agent-disabled {
173
+.agent-unavailable {
174 174
   opacity: 0.5;
175 175
 }
176 176
 

+ 1 - 1
app/concerns/twitter_concern.rb

@@ -5,7 +5,7 @@ module TwitterConcern
5 5
     include Oauthable
6 6
 
7 7
     validate :validate_twitter_options
8
-    valid_oauth_providers :twitter
8
+    valid_oauth_providers 'twitter'
9 9
   end
10 10
 
11 11
   def validate_twitter_options

+ 2 - 4
app/concerns/weibo_concern.rb

@@ -2,6 +2,8 @@ module WeiboConcern
2 2
   extend ActiveSupport::Concern
3 3
 
4 4
   included do
5
+    gem_dependency_check { defined?(WeiboOAuth2) }
6
+
5 7
     self.validate :validate_weibo_options
6 8
   end
7 9
 
@@ -22,8 +24,4 @@ module WeiboConcern
22 24
     end
23 25
     @weibo_client
24 26
   end
25
-
26
-  module ClassMethods
27
-
28
-  end
29 27
 end

+ 2 - 0
app/helpers/application_helper.rb

@@ -32,6 +32,8 @@ module ApplicationHelper
32 32
   def working(agent)
33 33
     if agent.disabled?
34 34
       link_to 'Disabled', agent_path(agent), class: 'label label-warning'
35
+    elsif agent.dependencies_missing?
36
+      content_tag :span, 'Missing Gems', class: 'label label-danger'
35 37
     elsif agent.working?
36 38
       content_tag :span, 'Yes', class: 'label label-success'
37 39
     else

+ 5 - 5
app/helpers/dot_helper.rb

@@ -137,9 +137,9 @@ module DotHelper
137 137
              label: agent_label[agent],
138 138
              tooltip: (agent.short_type.titleize if rich),
139 139
              URL: (agent_url[agent] if rich),
140
-             style: ('rounded,dashed' if agent.disabled?),
141
-             color: (@disabled if agent.disabled?),
142
-             fontcolor: (@disabled if agent.disabled?))
140
+             style: ('rounded,dashed' if agent.unavailable?),
141
+             color: (@disabled if agent.unavailable?),
142
+             fontcolor: (@disabled if agent.unavailable?))
143 143
       end
144 144
 
145 145
       def agent_edge(agent, receiver)
@@ -148,7 +148,7 @@ module DotHelper
148 148
              style: ('dashed' unless receiver.propagate_immediately?),
149 149
              label: (" #{agent.control_action}s " if agent.can_control_other_agents?),
150 150
              arrowhead: ('empty' if agent.can_control_other_agents?),
151
-             color: (@disabled if agent.disabled? || receiver.disabled?))
151
+             color: (@disabled if agent.unavailable? || receiver.unavailable?))
152 152
       end
153 153
 
154 154
       block('digraph', 'Agent Event Flow') {
@@ -218,7 +218,7 @@ module DotHelper
218 218
             # a dummy label only to obtain the background color
219 219
             label['class'] = [
220 220
               'label',
221
-              if agent.disabled?
221
+              if agent.unavailable?
222 222
                 'label-warning'
223 223
               elsif agent.working?
224 224
                 'label-success'

+ 0 - 5
app/helpers/service_helper.rb

@@ -1,5 +0,0 @@
1
-module ServiceHelper
2
-  def has_oauth_configuration_for(provider)
3
-    ENV["#{provider.upcase}_OAUTH_KEY"].present? && ENV["#{provider.upcase}_OAUTH_SECRET"].present?
4
-  end
5
-end

+ 19 - 2
app/models/agent.rb

@@ -150,6 +150,14 @@ class Agent < ActiveRecord::Base
150 150
     end
151 151
   end
152 152
 
153
+  def unavailable?
154
+    disabled? || dependencies_missing?
155
+  end
156
+
157
+  def dependencies_missing?
158
+    self.class.dependencies_missing?
159
+  end
160
+
153 161
   def default_schedule
154 162
     self.class.default_schedule
155 163
   end
@@ -317,6 +325,15 @@ class Agent < ActiveRecord::Base
317 325
       include? AgentControllerConcern
318 326
     end
319 327
 
328
+    def gem_dependency_check
329
+      @gem_dependencies_checked = true
330
+      @gem_dependencies_met = yield
331
+    end
332
+
333
+    def dependencies_missing?
334
+      @gem_dependencies_checked && !@gem_dependencies_met
335
+    end
336
+
320 337
     # Find all Agents that have received Events since the last execution of this method.  Update those Agents with
321 338
     # their new `last_checked_event_id` and queue each of the Agents to be called with #receive using `async_receive`.
322 339
     # This is called by bin/schedule.rb periodically.
@@ -362,7 +379,7 @@ class Agent < ActiveRecord::Base
362 379
     def async_receive(agent_id, event_ids)
363 380
       agent = Agent.find(agent_id)
364 381
       begin
365
-        return if agent.disabled?
382
+        return if agent.unavailable?
366 383
         agent.receive(Event.where(:id => event_ids))
367 384
         agent.last_receive_at = Time.now
368 385
         agent.save!
@@ -400,7 +417,7 @@ class Agent < ActiveRecord::Base
400 417
     def async_check(agent_id)
401 418
       agent = Agent.find(agent_id)
402 419
       begin
403
-        return if agent.disabled?
420
+        return if agent.unavailable?
404 421
         agent.check
405 422
         agent.last_check_at = Time.now
406 423
         agent.save!

+ 9 - 9
app/models/agents/ftpsite_agent.rb

@@ -1,15 +1,15 @@
1
-require 'net/ftp'
2
-require 'net/ftp/list'
3 1
 require 'uri'
4 2
 require 'time'
5 3
 
6 4
 module Agents
7 5
   class FtpsiteAgent < Agent
8 6
     cannot_receive_events!
9
-
10 7
     default_schedule "every_12h"
11 8
 
9
+    gem_dependency_check { defined?(Net::FTP) && defined?(Net::FTP::List) }
10
+
12 11
     description <<-MD
12
+      #{'## Include `net-ftp-list` in your Gemfile to use this Agent!' if dependencies_missing?}
13 13
       The FtpsiteAgent checks a FTP site and creates Events based on newly uploaded files in a directory.
14 14
 
15 15
       Specify a `url` that represents a directory of an FTP site to watch, and a list of `patterns` to match against file names.
@@ -35,12 +35,12 @@ module Agents
35 35
 
36 36
     def default_options
37 37
       {
38
-          'expected_update_period_in_days' => "1",
39
-          'url' => "ftp://example.org/pub/releases/",
40
-          'patterns' => [
41
-            'foo-*.tar.gz',
42
-          ],
43
-          'after' => Time.now.iso8601,
38
+        'expected_update_period_in_days' => "1",
39
+        'url' => "ftp://example.org/pub/releases/",
40
+        'patterns' => [
41
+          'foo-*.tar.gz',
42
+        ],
43
+        'after' => Time.now.iso8601,
44 44
       }
45 45
     end
46 46
 

+ 3 - 0
app/models/agents/google_calendar_publish_agent.rb

@@ -4,7 +4,10 @@ module Agents
4 4
   class GoogleCalendarPublishAgent < Agent
5 5
     cannot_be_scheduled!
6 6
 
7
+    gem_dependency_check { defined?(GoogleCalendar) }
8
+
7 9
     description <<-MD
10
+      #{'## Include `google-api-client` in your Gemfile to use this Agent!' if dependencies_missing?}
8 11
       The GoogleCalendarPublishAgent creates events on your google calendar.
9 12
 
10 13
       This agent relies on service accounts, rather than oauth.

+ 5 - 4
app/models/agents/growl_agent.rb

@@ -1,5 +1,3 @@
1
-require 'ruby-growl'
2
-
3 1
 module Agents
4 2
   class GrowlAgent < Agent
5 3
     attr_reader :growler
@@ -7,7 +5,10 @@ module Agents
7 5
     cannot_be_scheduled!
8 6
     cannot_create_events!
9 7
 
8
+    gem_dependency_check { defined?(Growl) }
9
+
10 10
     description <<-MD
11
+      #{'## Include `ruby-growl` in your Gemfile to use this Agent!' if dependencies_missing?}
11 12
       The GrowlAgent sends any events it receives to a Growl GNTP server immediately.
12 13
       
13 14
       It is assumed that events have a `message` or `text` key, which will hold the body of the growl notification, and a `subject` key, which will have the headline of the Growl notification. You can use Event Formatting Agent if your event does not provide these keys.
@@ -34,13 +35,13 @@ module Agents
34 35
         errors.add(:base, "growl_server and expected_receive_period_in_days are required fields")
35 36
       end
36 37
     end
37
-    
38
+
38 39
     def register_growl
39 40
       @growler = Growl.new interpolated['growl_server'], interpolated['growl_app_name'], "GNTP"
40 41
       @growler.password = interpolated['growl_password']
41 42
       @growler.add_notification interpolated['growl_notification_name']
42 43
     end
43
-    
44
+
44 45
     def notify_growl(subject, message)
45 46
       @growler.notify(interpolated['growl_notification_name'], subject, message)
46 47
     end

+ 7 - 1
app/models/agents/hipchat_agent.rb

@@ -3,7 +3,10 @@ module Agents
3 3
     cannot_be_scheduled!
4 4
     cannot_create_events!
5 5
 
6
+    gem_dependency_check { defined?(HipChat) }
7
+
6 8
     description <<-MD
9
+      #{'## Include `hipchat` in your Gemfile to use this Agent!' if dependencies_missing?}
7 10
       The HipchatAgent sends messages to a Hipchat Room
8 11
 
9 12
       To authenticate you need to set the `auth_token`, you can get one at your Hipchat Group Admin page which you can find here:
@@ -40,11 +43,14 @@ module Agents
40 43
     end
41 44
 
42 45
     def receive(incoming_events)
43
-      client = HipChat::Client.new(interpolated[:auth_token] || credential('hipchat_auth_token'))
44 46
       incoming_events.each do |event|
45 47
         mo = interpolated(event)
46 48
         client[mo[:room_name]].send(mo[:username][0..14], mo[:message], :notify => boolify(mo[:notify]), :color => mo[:color])
47 49
       end
48 50
     end
51
+
52
+    def client
53
+      @client ||= HipChat::Client.new(interpolated[:auth_token] || credential('hipchat_auth_token'))
54
+    end
49 55
   end
50 56
 end

+ 212 - 208
app/models/agents/human_task_agent.rb

@@ -1,10 +1,11 @@
1
-require 'rturk'
2
-
3 1
 module Agents
4 2
   class HumanTaskAgent < Agent
5 3
     default_schedule "every_10m"
6 4
 
5
+    gem_dependency_check { defined?(RTurk) }
6
+
7 7
     description <<-MD
8
+      #{'## Include `rturk` in your Gemfile to use this Agent!' if dependencies_missing?}
8 9
       You can use a HumanTaskAgent to create Human Intelligence Tasks (HITs) on Mechanical Turk.
9 10
 
10 11
       HITs can be created in response to events, or on a schedule.  Set `trigger_on` to either `schedule` or `event`.
@@ -226,266 +227,269 @@ module Agents
226 227
 
227 228
     protected
228 229
 
229
-    def take_majority?
230
-      interpolated['combination_mode'] == "take_majority" || interpolated['take_majority'] == "true"
231
-    end
230
+    if defined?(RTurk)
232 231
 
233
-    def create_poll?
234
-      interpolated['combination_mode'] == "poll"
235
-    end
232
+      def take_majority?
233
+        interpolated['combination_mode'] == "take_majority" || interpolated['take_majority'] == "true"
234
+      end
236 235
 
237
-    def event_for_hit(hit_id)
238
-      if memory['hits'][hit_id].is_a?(Hash)
239
-        Event.find_by_id(memory['hits'][hit_id]['event_id'])
240
-      else
241
-        nil
236
+      def create_poll?
237
+        interpolated['combination_mode'] == "poll"
242 238
       end
243
-    end
244 239
 
245
-    def hit_type(hit_id)
246
-      if memory['hits'][hit_id].is_a?(Hash) && memory['hits'][hit_id]['type']
247
-        memory['hits'][hit_id]['type']
248
-      else
249
-        'user'
240
+      def event_for_hit(hit_id)
241
+        if memory['hits'][hit_id].is_a?(Hash)
242
+          Event.find_by_id(memory['hits'][hit_id]['event_id'])
243
+        else
244
+          nil
245
+        end
250 246
       end
251
-    end
252 247
 
253
-    def review_hits
254
-      reviewable_hit_ids = RTurk::GetReviewableHITs.create.hit_ids
255
-      my_reviewed_hit_ids = reviewable_hit_ids & (memory['hits'] || {}).keys
256
-      if reviewable_hit_ids.length > 0
257
-        log "MTurk reports #{reviewable_hit_ids.length} HITs, of which I own [#{my_reviewed_hit_ids.to_sentence}]"
248
+      def hit_type(hit_id)
249
+        if memory['hits'][hit_id].is_a?(Hash) && memory['hits'][hit_id]['type']
250
+          memory['hits'][hit_id]['type']
251
+        else
252
+          'user'
253
+        end
258 254
       end
259 255
 
260
-      my_reviewed_hit_ids.each do |hit_id|
261
-        hit = RTurk::Hit.new(hit_id)
262
-        assignments = hit.assignments
256
+      def review_hits
257
+        reviewable_hit_ids = RTurk::GetReviewableHITs.create.hit_ids
258
+        my_reviewed_hit_ids = reviewable_hit_ids & (memory['hits'] || {}).keys
259
+        if reviewable_hit_ids.length > 0
260
+          log "MTurk reports #{reviewable_hit_ids.length} HITs, of which I own [#{my_reviewed_hit_ids.to_sentence}]"
261
+        end
262
+
263
+        my_reviewed_hit_ids.each do |hit_id|
264
+          hit = RTurk::Hit.new(hit_id)
265
+          assignments = hit.assignments
263 266
 
264
-        log "Looking at HIT #{hit_id}.  I found #{assignments.length} assignments#{" with the statuses: #{assignments.map(&:status).to_sentence}" if assignments.length > 0}"
265
-        if assignments.length == hit.max_assignments && assignments.all? { |assignment| assignment.status == "Submitted" }
266
-          inbound_event = event_for_hit(hit_id)
267
+          log "Looking at HIT #{hit_id}.  I found #{assignments.length} assignments#{" with the statuses: #{assignments.map(&:status).to_sentence}" if assignments.length > 0}"
268
+          if assignments.length == hit.max_assignments && assignments.all? { |assignment| assignment.status == "Submitted" }
269
+            inbound_event = event_for_hit(hit_id)
267 270
 
268
-          if hit_type(hit_id) == 'poll'
269
-            # handle completed polls
271
+            if hit_type(hit_id) == 'poll'
272
+              # handle completed polls
270 273
 
271
-            log "Handling a poll: #{hit_id}"
274
+              log "Handling a poll: #{hit_id}"
272 275
 
273
-            scores = {}
274
-            assignments.each do |assignment|
275
-              assignment.answers.each do |index, rating|
276
-                scores[index] ||= 0
277
-                scores[index] += rating.to_i
276
+              scores = {}
277
+              assignments.each do |assignment|
278
+                assignment.answers.each do |index, rating|
279
+                  scores[index] ||= 0
280
+                  scores[index] += rating.to_i
281
+                end
278 282
               end
279
-            end
280 283
 
281
-            top_answer = scores.to_a.sort {|b, a| a.last <=> b.last }.first.first
284
+              top_answer = scores.to_a.sort {|b, a| a.last <=> b.last }.first.first
282 285
 
283
-            payload = {
284
-              'answers' => memory['hits'][hit_id]['answers'],
285
-              'poll' => assignments.map(&:answers),
286
-              'best_answer' => memory['hits'][hit_id]['answers'][top_answer.to_i - 1]
287
-            }
286
+              payload = {
287
+                'answers' => memory['hits'][hit_id]['answers'],
288
+                'poll' => assignments.map(&:answers),
289
+                'best_answer' => memory['hits'][hit_id]['answers'][top_answer.to_i - 1]
290
+              }
288 291
 
289
-            event = create_event :payload => payload
290
-            log "Event emitted with answer(s) for poll", :outbound_event => event, :inbound_event => inbound_event
291
-          else
292
-            # handle normal completed HITs
293
-            payload = { 'answers' => assignments.map(&:answers) }
294
-
295
-            if take_majority?
296
-              counts = {}
297
-              options['hit']['questions'].each do |question|
298
-                question_counts = question['selections'].inject({}) { |memo, selection| memo[selection['key']] = 0; memo }
299
-                assignments.each do |assignment|
300
-                  answers = ActiveSupport::HashWithIndifferentAccess.new(assignment.answers)
301
-                  answer = answers[question['key']]
302
-                  question_counts[answer] += 1
292
+              event = create_event :payload => payload
293
+              log "Event emitted with answer(s) for poll", :outbound_event => event, :inbound_event => inbound_event
294
+            else
295
+              # handle normal completed HITs
296
+              payload = { 'answers' => assignments.map(&:answers) }
297
+
298
+              if take_majority?
299
+                counts = {}
300
+                options['hit']['questions'].each do |question|
301
+                  question_counts = question['selections'].inject({}) { |memo, selection| memo[selection['key']] = 0; memo }
302
+                  assignments.each do |assignment|
303
+                    answers = ActiveSupport::HashWithIndifferentAccess.new(assignment.answers)
304
+                    answer = answers[question['key']]
305
+                    question_counts[answer] += 1
306
+                  end
307
+                  counts[question['key']] = question_counts
303 308
                 end
304
-                counts[question['key']] = question_counts
305
-              end
306
-              payload['counts'] = counts
309
+                payload['counts'] = counts
307 310
 
308
-              majority_answer = counts.inject({}) do |memo, (key, question_counts)|
309
-                memo[key] = question_counts.to_a.sort {|a, b| a.last <=> b.last }.last.first
310
-                memo
311
-              end
312
-              payload['majority_answer'] = majority_answer
313
-
314
-              if all_questions_are_numeric?
315
-                average_answer = counts.inject({}) do |memo, (key, question_counts)|
316
-                  sum = divisor = 0
317
-                  question_counts.to_a.each do |num, count|
318
-                    sum += num.to_s.to_f * count
319
-                    divisor += count
320
-                  end
321
-                  memo[key] = sum / divisor.to_f
311
+                majority_answer = counts.inject({}) do |memo, (key, question_counts)|
312
+                  memo[key] = question_counts.to_a.sort {|a, b| a.last <=> b.last }.last.first
322 313
                   memo
323 314
                 end
324
-                payload['average_answer'] = average_answer
325
-              end
326
-            end
327
-
328
-            if create_poll?
329
-              questions = []
330
-              selections = 5.times.map { |i| { 'key' => i+1, 'text' => i+1 } }.reverse
331
-              assignments.length.times do |index|
332
-                questions << {
333
-                  'type' => "selection",
334
-                  'name' => "Item #{index + 1}",
335
-                  'key' => index,
336
-                  'required' => "true",
337
-                  'question' => interpolate_string(options['poll_options']['row_template'], assignments[index].answers),
338
-                  'selections' => selections
339
-                }
315
+                payload['majority_answer'] = majority_answer
316
+
317
+                if all_questions_are_numeric?
318
+                  average_answer = counts.inject({}) do |memo, (key, question_counts)|
319
+                    sum = divisor = 0
320
+                    question_counts.to_a.each do |num, count|
321
+                      sum += num.to_s.to_f * count
322
+                      divisor += count
323
+                    end
324
+                    memo[key] = sum / divisor.to_f
325
+                    memo
326
+                  end
327
+                  payload['average_answer'] = average_answer
328
+                end
340 329
               end
341 330
 
342
-              poll_hit = create_hit 'title' => options['poll_options']['title'],
343
-                                    'description' => options['poll_options']['instructions'],
344
-                                    'questions' => questions,
345
-                                    'assignments' => options['poll_options']['assignments'],
346
-                                    'lifetime_in_seconds' => options['poll_options']['lifetime_in_seconds'],
347
-                                    'reward' => options['poll_options']['reward'],
348
-                                    'payload' => inbound_event && inbound_event.payload,
349
-                                    'metadata' => { 'type' => 'poll',
350
-                                                    'original_hit' => hit_id,
351
-                                                    'answers' => assignments.map(&:answers),
352
-                                                    'event_id' => inbound_event && inbound_event.id }
353
-
354
-              log "Poll HIT created with ID #{poll_hit.id} and URL #{poll_hit.url}.  Original HIT: #{hit_id}", :inbound_event => inbound_event
355
-            else
356
-              if options[:separate_answers]
357
-                payload['answers'].each.with_index do |answer, index|
358
-                  sub_payload = payload.dup
359
-                  sub_payload.delete('answers')
360
-                  sub_payload['answer'] = answer
361
-                  event = create_event :payload => sub_payload
362
-                  log "Event emitted with answer ##{index}", :outbound_event => event, :inbound_event => inbound_event
331
+              if create_poll?
332
+                questions = []
333
+                selections = 5.times.map { |i| { 'key' => i+1, 'text' => i+1 } }.reverse
334
+                assignments.length.times do |index|
335
+                  questions << {
336
+                    'type' => "selection",
337
+                    'name' => "Item #{index + 1}",
338
+                    'key' => index,
339
+                    'required' => "true",
340
+                    'question' => interpolate_string(options['poll_options']['row_template'], assignments[index].answers),
341
+                    'selections' => selections
342
+                  }
363 343
                 end
344
+
345
+                poll_hit = create_hit 'title' => options['poll_options']['title'],
346
+                                      'description' => options['poll_options']['instructions'],
347
+                                      'questions' => questions,
348
+                                      'assignments' => options['poll_options']['assignments'],
349
+                                      'lifetime_in_seconds' => options['poll_options']['lifetime_in_seconds'],
350
+                                      'reward' => options['poll_options']['reward'],
351
+                                      'payload' => inbound_event && inbound_event.payload,
352
+                                      'metadata' => { 'type' => 'poll',
353
+                                                      'original_hit' => hit_id,
354
+                                                      'answers' => assignments.map(&:answers),
355
+                                                      'event_id' => inbound_event && inbound_event.id }
356
+
357
+                log "Poll HIT created with ID #{poll_hit.id} and URL #{poll_hit.url}.  Original HIT: #{hit_id}", :inbound_event => inbound_event
364 358
               else
365
-                event = create_event :payload => payload
366
-                log "Event emitted with answer(s)", :outbound_event => event, :inbound_event => inbound_event
359
+                if options[:separate_answers]
360
+                  payload['answers'].each.with_index do |answer, index|
361
+                    sub_payload = payload.dup
362
+                    sub_payload.delete('answers')
363
+                    sub_payload['answer'] = answer
364
+                    event = create_event :payload => sub_payload
365
+                    log "Event emitted with answer ##{index}", :outbound_event => event, :inbound_event => inbound_event
366
+                  end
367
+                else
368
+                  event = create_event :payload => payload
369
+                  log "Event emitted with answer(s)", :outbound_event => event, :inbound_event => inbound_event
370
+                end
367 371
               end
368 372
             end
369
-          end
370 373
 
371
-          assignments.each(&:approve!)
372
-          hit.dispose!
374
+            assignments.each(&:approve!)
375
+            hit.dispose!
373 376
 
374
-          memory['hits'].delete(hit_id)
377
+            memory['hits'].delete(hit_id)
378
+          end
375 379
         end
376 380
       end
377
-    end
378 381
 
379
-    def all_questions_are_numeric?
380
-      interpolated['hit']['questions'].all? do |question|
381
-        question['selections'].all? do |selection|
382
-          selection['key'] == selection['key'].to_f.to_s || selection['key'] == selection['key'].to_i.to_s
382
+      def all_questions_are_numeric?
383
+        interpolated['hit']['questions'].all? do |question|
384
+          question['selections'].all? do |selection|
385
+            selection['key'] == selection['key'].to_f.to_s || selection['key'] == selection['key'].to_i.to_s
386
+          end
383 387
         end
384 388
       end
385
-    end
386
-
387
-    def create_basic_hit(event = nil)
388
-      hit = create_hit 'title' => options['hit']['title'],
389
-                       'description' => options['hit']['description'],
390
-                       'questions' => options['hit']['questions'],
391
-                       'assignments' => options['hit']['assignments'],
392
-                       'lifetime_in_seconds' => options['hit']['lifetime_in_seconds'],
393
-                       'reward' => options['hit']['reward'],
394
-                       'payload' => event && event.payload,
395
-                       'metadata' => { 'event_id' => event && event.id }
396
-
397
-      log "HIT created with ID #{hit.id} and URL #{hit.url}", :inbound_event => event
398
-    end
399 389
 
400
-    def create_hit(opts = {})
401
-      payload = opts['payload'] || {}
402
-      title = interpolate_string(opts['title'], payload).strip
403
-      description = interpolate_string(opts['description'], payload).strip
404
-      questions = interpolate_options(opts['questions'], payload)
405
-      hit = RTurk::Hit.create(:title => title) do |hit|
406
-        hit.max_assignments = (opts['assignments'] || 1).to_i
407
-        hit.description = description
408
-        hit.lifetime = (opts['lifetime_in_seconds'] || 24 * 60 * 60).to_i
409
-        hit.question_form AgentQuestionForm.new(:title => title, :description => description, :questions => questions)
410
-        hit.reward = (opts['reward'] || 0.05).to_f
411
-        #hit.qualifications.add :approval_rate, { :gt => 80 }
390
+      def create_basic_hit(event = nil)
391
+        hit = create_hit 'title' => options['hit']['title'],
392
+                         'description' => options['hit']['description'],
393
+                         'questions' => options['hit']['questions'],
394
+                         'assignments' => options['hit']['assignments'],
395
+                         'lifetime_in_seconds' => options['hit']['lifetime_in_seconds'],
396
+                         'reward' => options['hit']['reward'],
397
+                         'payload' => event && event.payload,
398
+                         'metadata' => { 'event_id' => event && event.id }
399
+
400
+        log "HIT created with ID #{hit.id} and URL #{hit.url}", :inbound_event => event
412 401
       end
413
-      memory['hits'] ||= {}
414
-      memory['hits'][hit.id] = opts['metadata'] || {}
415
-      hit
416
-    end
417 402
 
418
-    # RTurk Question Form
403
+      def create_hit(opts = {})
404
+        payload = opts['payload'] || {}
405
+        title = interpolate_string(opts['title'], payload).strip
406
+        description = interpolate_string(opts['description'], payload).strip
407
+        questions = interpolate_options(opts['questions'], payload)
408
+        hit = RTurk::Hit.create(:title => title) do |hit|
409
+          hit.max_assignments = (opts['assignments'] || 1).to_i
410
+          hit.description = description
411
+          hit.lifetime = (opts['lifetime_in_seconds'] || 24 * 60 * 60).to_i
412
+          hit.question_form AgentQuestionForm.new(:title => title, :description => description, :questions => questions)
413
+          hit.reward = (opts['reward'] || 0.05).to_f
414
+          #hit.qualifications.add :approval_rate, { :gt => 80 }
415
+        end
416
+        memory['hits'] ||= {}
417
+        memory['hits'][hit.id] = opts['metadata'] || {}
418
+        hit
419
+      end
419 420
 
420
-    class AgentQuestionForm < RTurk::QuestionForm
421
-      needs :title, :description, :questions
421
+      # RTurk Question Form
422 422
 
423
-      def question_form_content
424
-        Overview do
425
-          Title do
426
-            text @title
427
-          end
428
-          Text do
429
-            text @description
430
-          end
431
-        end
423
+      class AgentQuestionForm < RTurk::QuestionForm
424
+        needs :title, :description, :questions
432 425
 
433
-        @questions.each.with_index do |question, index|
434
-          Question do
435
-            QuestionIdentifier do
436
-              text question['key'] || "question_#{index}"
426
+        def question_form_content
427
+          Overview do
428
+            Title do
429
+              text @title
437 430
             end
438
-            DisplayName do
439
-              text question['name'] || "Question ##{index}"
431
+            Text do
432
+              text @description
440 433
             end
441
-            IsRequired do
442
-              text question['required'] || 'true'
443
-            end
444
-            QuestionContent do
445
-              Text do
446
-                text question['question']
434
+          end
435
+
436
+          @questions.each.with_index do |question, index|
437
+            Question do
438
+              QuestionIdentifier do
439
+                text question['key'] || "question_#{index}"
447 440
               end
448
-            end
449
-            AnswerSpecification do
450
-              if question['type'] == "selection"
441
+              DisplayName do
442
+                text question['name'] || "Question ##{index}"
443
+              end
444
+              IsRequired do
445
+                text question['required'] || 'true'
446
+              end
447
+              QuestionContent do
448
+                Text do
449
+                  text question['question']
450
+                end
451
+              end
452
+              AnswerSpecification do
453
+                if question['type'] == "selection"
451 454
 
452
-                SelectionAnswer do
453
-                  StyleSuggestion do
454
-                    text 'radiobutton'
455
-                  end
456
-                  Selections do
457
-                    question['selections'].each do |selection|
458
-                      Selection do
459
-                        SelectionIdentifier do
460
-                          text selection['key']
461
-                        end
462
-                        Text do
463
-                          text selection['text']
455
+                  SelectionAnswer do
456
+                    StyleSuggestion do
457
+                      text 'radiobutton'
458
+                    end
459
+                    Selections do
460
+                      question['selections'].each do |selection|
461
+                        Selection do
462
+                          SelectionIdentifier do
463
+                            text selection['key']
464
+                          end
465
+                          Text do
466
+                            text selection['text']
467
+                          end
464 468
                         end
465 469
                       end
466 470
                     end
467 471
                   end
468
-                end
469 472
 
470
-              else
473
+                else
471 474
 
472
-                FreeTextAnswer do
473
-                  if question['min_length'].present? || question['max_length'].present?
474
-                    Constraints do
475
-                      lengths = {}
476
-                      lengths['minLength'] = question['min_length'].to_s if question['min_length'].present?
477
-                      lengths['maxLength'] = question['max_length'].to_s if question['max_length'].present?
478
-                      Length lengths
475
+                  FreeTextAnswer do
476
+                    if question['min_length'].present? || question['max_length'].present?
477
+                      Constraints do
478
+                        lengths = {}
479
+                        lengths['minLength'] = question['min_length'].to_s if question['min_length'].present?
480
+                        lengths['maxLength'] = question['max_length'].to_s if question['max_length'].present?
481
+                        Length lengths
482
+                      end
479 483
                     end
480
-                  end
481 484
 
482
-                  if question['default'].present?
483
-                    DefaultText do
484
-                      text question['default']
485
+                    if question['default'].present?
486
+                      DefaultText do
487
+                        text question['default']
488
+                      end
485 489
                     end
486 490
                   end
487
-                end
488 491
 
492
+                end
489 493
               end
490 494
             end
491 495
           end

+ 3 - 0
app/models/agents/jabber_agent.rb

@@ -3,7 +3,10 @@ module Agents
3 3
     cannot_be_scheduled!
4 4
     cannot_create_events!
5 5
 
6
+    gem_dependency_check { defined?(Jabber) }
7
+
6 8
     description <<-MD
9
+      #{'## Include `xmpp4r` in your Gemfile to use this Agent!' if dependencies_missing?}
7 10
       The JabberAgent will send any events it receives to your Jabber/XMPP IM account.
8 11
 
9 12
       Specify the `jabber_server` and `jabber_port` for your Jabber server.

+ 3 - 1
app/models/agents/mqtt_agent.rb

@@ -1,10 +1,12 @@
1 1
 # encoding: utf-8 
2
-require "mqtt"
3 2
 require "json"
4 3
 
5 4
 module Agents
6 5
   class MqttAgent < Agent
6
+    gem_dependency_check { defined?(MQTT) }
7
+
7 8
     description <<-MD
9
+      #{'## Include `mqtt` in your Gemfile to use this Agent!' if dependencies_missing?}
8 10
       The MQTT agent allows both publication and subscription to an MQTT topic.
9 11
 
10 12
       MQTT is a generic transport protocol for machine to machine communication.

+ 6 - 2
app/models/agents/slack_agent.rb

@@ -1,11 +1,15 @@
1 1
 module Agents
2 2
   class SlackAgent < Agent
3
+    DEFAULT_WEBHOOK = 'incoming-webhook'
4
+    DEFAULT_USERNAME = 'Huginn'
5
+
3 6
     cannot_be_scheduled!
4 7
     cannot_create_events!
5 8
 
6
-    DEFAULT_WEBHOOK = 'incoming-webhook'
7
-    DEFAULT_USERNAME = 'Huginn'
9
+    gem_dependency_check { defined?(Slack) }
10
+
8 11
     description <<-MD
12
+      #{'## Include `slack-notifier` in your Gemfile to use this Agent!' if dependencies_missing?}
9 13
       The SlackAgent lets you receive events and send notifications to [slack](https://slack.com/).
10 14
 
11 15
       To get started, you will first need to setup an incoming webhook.

+ 13 - 8
app/models/agents/twilio_agent.rb

@@ -1,4 +1,3 @@
1
-require 'twilio-ruby'
2 1
 require 'securerandom'
3 2
 
4 3
 module Agents
@@ -6,7 +5,10 @@ module Agents
6 5
     cannot_be_scheduled!
7 6
     cannot_create_events!
8 7
 
8
+    gem_dependency_check { defined?(Twilio) }
9
+
9 10
     description <<-MD
11
+      #{'## Include `twilio-ruby` in your Gemfile to use this Agent!' if dependencies_missing?}
10 12
       The TwilioAgent receives and collects events and sends them via text message (up to 160 characters) or gives you a call when scheduled.
11 13
 
12 14
       It is assumed that events have a `message`, `text`, or `sms` key, the value of which is sent as the content of the text message/call. You can use the EventFormattingAgent if your event does not provide these keys.
@@ -39,7 +41,6 @@ module Agents
39 41
     end
40 42
 
41 43
     def receive(incoming_events)
42
-      @client = Twilio::REST::Client.new interpolated['account_sid'], interpolated['auth_token']
43 44
       memory['pending_calls'] ||= {}
44 45
       incoming_events.each do |event|
45 46
         message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s
@@ -63,15 +64,15 @@ module Agents
63 64
     end
64 65
 
65 66
     def send_message(message)
66
-      @client.account.sms.messages.create :from => interpolated['sender_cell'],
67
-                                          :to => interpolated['receiver_cell'],
68
-                                          :body => message
67
+      client.account.sms.messages.create :from => interpolated['sender_cell'],
68
+                                         :to => interpolated['receiver_cell'],
69
+                                         :body => message
69 70
     end
70 71
 
71 72
     def make_call(secret)
72
-      @client.account.calls.create :from => interpolated['sender_cell'],
73
-                                   :to => interpolated['receiver_cell'],
74
-                                   :url => post_url(interpolated['server_url'], secret)
73
+      client.account.calls.create :from => interpolated['sender_cell'],
74
+                                  :to => interpolated['receiver_cell'],
75
+                                  :url => post_url(interpolated['server_url'], secret)
75 76
     end
76 77
 
77 78
     def post_url(server_url, secret)
@@ -85,5 +86,9 @@ module Agents
85 86
         [response.text, 200]
86 87
       end
87 88
     end
89
+
90
+    def client
91
+      @client ||= Twilio::REST::Client.new interpolated['account_sid'], interpolated['auth_token']
92
+    end
88 93
   end
89 94
 end

+ 3 - 0
app/models/agents/weather_agent.rb

@@ -5,7 +5,10 @@ module Agents
5 5
   class WeatherAgent < Agent
6 6
     cannot_receive_events!
7 7
 
8
+    gem_dependency_check { defined?(Wunderground) && defined?(ForecastIO) }
9
+
8 10
     description <<-MD
11
+      #{'## Include `forecast_io` and `wunderground` in your Gemfile to use this Agent!' if dependencies_missing?}
9 12
       The WeatherAgent creates an event for the day's weather at a given `location`.
10 13
 
11 14
       You also must select `which_day` you would like to get the weather for where the number 0 is for today and 1 is for tomorrow and so on. Weather is only returned for 1 week at a time.

+ 2 - 3
app/models/agents/weibo_publish_agent.rb

@@ -1,5 +1,4 @@
1 1
 # encoding: utf-8 
2
-require "weibo_2"
3 2
 
4 3
 module Agents
5 4
   class WeiboPublishAgent < Agent
@@ -8,6 +7,7 @@ module Agents
8 7
     cannot_be_scheduled!
9 8
 
10 9
     description <<-MD
10
+      #{'## Include `weibo_2` in your Gemfile to use this Agent!' if dependencies_missing?}
11 11
       The WeiboPublishAgent publishes tweets from the events it receives.
12 12
 
13 13
       You must first set up a Weibo app and generate an `acess_token` for the user to send statuses as.
@@ -79,8 +79,7 @@ module Agents
79 79
       tweet_json[:entities][:urls].each do |url|
80 80
         text.gsub! url[:url], url[:expanded_url]
81 81
       end
82
-      return text
82
+      text
83 83
     end
84
-
85 84
   end
86 85
 end

+ 1 - 1
app/models/agents/weibo_user_agent.rb

@@ -1,5 +1,4 @@
1 1
 # encoding: utf-8 
2
-require "weibo_2"
3 2
 
4 3
 module Agents
5 4
   class WeiboUserAgent < Agent
@@ -8,6 +7,7 @@ module Agents
8 7
     cannot_receive_events!
9 8
 
10 9
     description <<-MD
10
+      #{'## Include `weibo_2` in your Gemfile to use this Agent!' if dependencies_missing?}
11 11
       The WeiboUserAgent follows the timeline of a specified Weibo user. It uses this endpoint: http://open.weibo.com/wiki/2/statuses/user_timeline/en
12 12
 
13 13
       You must first set up a Weibo app and generate an `acess_token` to authenticate with. Provide that, along with the `app_key` and `app_secret` for your Weibo app in the options.

+ 6 - 6
app/views/agents/_table.html.erb

@@ -13,7 +13,7 @@
13 13
 
14 14
     <% @agents.each do |agent| %>
15 15
       <tr>
16
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
16
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
17 17
           <%= link_to agent.name, agent_path(agent) %>
18 18
           <br/>
19 19
           <span class='text-muted'><%= agent.short_type.titleize %></span>
@@ -23,35 +23,35 @@
23 23
             </span>
24 24
           <% end %>
25 25
         </td>
26
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
26
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
27 27
           <% if agent.can_be_scheduled? %>
28 28
             <%= agent_schedule(agent, ',<br/>') %>
29 29
           <% else %>
30 30
             <span class='not-applicable'></span>
31 31
           <% end %>
32 32
         </td>
33
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
33
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
34 34
           <% if agent.can_be_scheduled? %>
35 35
             <%= agent.last_check_at ? time_ago_in_words(agent.last_check_at) + " ago" : "never" %>
36 36
           <% else %>
37 37
             <span class='not-applicable'></span>
38 38
           <% end %>
39 39
         </td>
40
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
40
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
41 41
           <% if agent.can_create_events? %>
42 42
             <%= agent.last_event_at ? time_ago_in_words(agent.last_event_at) + " ago" : "never" %>
43 43
           <% else %>
44 44
             <span class='not-applicable'></span>
45 45
           <% end %>
46 46
         </td>
47
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
47
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
48 48
           <% if agent.can_receive_events? %>
49 49
             <%= agent.last_receive_at ? time_ago_in_words(agent.last_receive_at) + " ago" : "never" %>
50 50
           <% else %>
51 51
             <span class='not-applicable'></span>
52 52
           <% end %>
53 53
         </td>
54
-        <td class='<%= "agent-disabled" if agent.disabled? %>'>
54
+        <td class='<%= "agent-unavailable" if agent.unavailable? %>'>
55 55
           <% if agent.can_create_events? %>
56 56
             <%= link_to(agent.events_count || 0, agent_events_path(agent)) %>
57 57
           <% else %>

+ 3 - 3
app/views/services/index.html.erb

@@ -11,13 +11,13 @@
11 11
         <%= link_to 'wiki', 'https://github.com/cantino/huginn/wiki/Configuring-OAuth-applications', target: :_blank %>
12 12
         for guidance.
13 13
       </p>
14
-      <% if has_oauth_configuration_for('twitter') %>
14
+      <% if has_oauth_configuration_for?('twitter') %>
15 15
         <p><%= link_to "Authenticate with Twitter", "/auth/twitter" %></p>
16 16
       <% end %>
17
-      <% if has_oauth_configuration_for('thirty_seven_signals') %>
17
+      <% if has_oauth_configuration_for?('thirty_seven_signals') %>
18 18
         <p><%= link_to "Authenticate with 37Signals (Basecamp)", "/auth/37signals" %></p>
19 19
       <% end -%>
20
-      <% if has_oauth_configuration_for('github') %>
20
+      <% if has_oauth_configuration_for?('github') %>
21 21
         <p><%= link_to "Authenticate with Github", "/auth/github" %></p>
22 22
       <% end -%>
23 23
       <hr>

+ 1 - 1
config/initializers/aws.rb

@@ -1,4 +1,4 @@
1
-unless Rails.env.test?
1
+if defined?(RTurk) && !Rails.env.test?
2 2
   RTurk::logger.level = Logger::DEBUG
3 3
   RTurk.setup(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_ACCESS_KEY'], :sandbox => ENV['AWS_SANDBOX'] == "true")
4 4
 end

+ 21 - 3
config/initializers/omniauth.rb

@@ -1,5 +1,23 @@
1
+LOADED_OMNIAUTH_STRATEGIES = {
2
+  'twitter'   => defined?(OmniAuth::Strategies::Twitter),
3
+  '37signals' => defined?(OmniAuth::Strategies::ThirtySevenSignals),
4
+  'github'    => defined?(OmniAuth::Strategies::GitHub)
5
+}
6
+
7
+def has_oauth_configuration_for?(provider)
8
+  LOADED_OMNIAUTH_STRATEGIES[provider.to_s] && ENV["#{provider.upcase}_OAUTH_KEY"].present? && ENV["#{provider.upcase}_OAUTH_SECRET"].present?
9
+end
10
+
1 11
 Rails.application.config.middleware.use OmniAuth::Builder do
2
-  provider :twitter, ENV['TWITTER_OAUTH_KEY'], ENV['TWITTER_OAUTH_SECRET'], authorize_params: {force_login: 'true', use_authorize: 'true'}
3
-  provider '37signals', ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'], ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET']
4
-  provider :github, ENV['GITHUB_OAUTH_KEY'], ENV['GITHUB_OAUTH_SECRET']
12
+  if has_oauth_configuration_for?('twitter')
13
+    provider 'twitter', ENV['TWITTER_OAUTH_KEY'], ENV['TWITTER_OAUTH_SECRET'], authorize_params: {force_login: 'true', use_authorize: 'true'}
14
+  end
15
+
16
+  if has_oauth_configuration_for?('37signals')
17
+    provider '37signals', ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'], ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET']
18
+  end
19
+
20
+  if has_oauth_configuration_for?('github')
21
+    provider 'github', ENV['GITHUB_OAUTH_KEY'], ENV['GITHUB_OAUTH_SECRET']
22
+  end
5 23
 end

+ 1 - 1
lib/huginn_scheduler.rb

@@ -40,7 +40,7 @@ class Rufus::Scheduler
40 40
   def schedule_scheduler_agent(agent)
41 41
     job = scheduler_agent_job(agent)
42 42
 
43
-    if agent.disabled?
43
+    if agent.unavailable?
44 44
       if job
45 45
         puts "Unscheduling SchedulerAgent##{agent.id} (disabled)"
46 46
         job.unschedule